#include "helper.h"
#include "monitor.h"
#include "reg.h"

extern uint32_t instr;
extern char assembly[80];
extern FILE *trace_fp;
/* decode R-type instrucion */
static void decode_r_type(uint32_t instr)
{

	op_src1->type = OP_TYPE_REG;
	op_src1->reg = (instr & RS_MASK) >> (RT_SIZE + IMM_SIZE);
	op_src1->val = reg_w(op_src1->reg);

	op_src2->type = OP_TYPE_REG;
	op_src2->imm = (instr & RT_MASK) >> (RD_SIZE + SHAMT_SIZE + FUNC_SIZE);
	op_src2->val = reg_w(op_src2->reg);

	op_dest->type = OP_TYPE_REG;
	op_dest->reg = (instr & RD_MASK) >> (SHAMT_SIZE + FUNC_SIZE);
}

make_helper(and)
{
	decode_r_type(instr);
	reg_w(op_dest->reg) = (op_src1->val & op_src2->val);
	sprintf(assembly, "and   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
    fprintf(trace_fp, "0x%04x     %02d     0x%04x\n", cpu.pc, op_dest->reg, (op_src1->val & op_src2->val));
}

make_helper(add)
{
	decode_r_type(instr);
	uint32_t temp = op_src1->val + op_src2->val;
	uint32_t temp1 = (op_src1->val) & 0x80000000;
	uint32_t temp2 = (op_src2->val) & 0x80000000;
	uint32_t temp3 = temp & 0x80000000;
	if ((temp3 != temp1) && (temp3 != temp2))
	{
		// overflow
		//  Cause.ExcCode <- 0x0c(Ov)
		cp0_w(R_CAUSE) = (cp0_w(R_CAUSE) & 0xffffff83) | (0x0c << 2);
		// EPC <- pc
		cp0_w(R_EPC) = cpu.pc;
		// Status.EXL <- 1
		cp0_w(R_STATUS) = ((cp0_w(R_STATUS) & 0xfffffffd) | (0x1 << 1));

		// If Ov is in Delay_Slot...
		if (temu_state == JUMP)
		{
			// EPC <- EPC - 4
			cp0_w(R_EPC) -= 4;
			// Cause.BD <- 1
			cp0_w(R_CAUSE) = ((cp0_w(R_CAUSE) & 0x7fffffff) | 0x80000000);
		}

		reg_w(op_dest->reg) = (uint32_t)temp3;
		sprintf(assembly, "add     %s, %s, %s\nWARNING: Inetger_Overflow Exception.", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
		cpu.pc = 0xbfc00380;
	}
	else
	{
		reg_w(op_dest->reg) = temp;
		sprintf(assembly, "add   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	    fprintf(trace_fp, "0x%04x     %02d     0x%04x\n", cpu.pc, op_dest->reg, (temp));
	}
}

make_helper(addu)
{
	decode_r_type(instr);
	reg_w(op_dest->reg) = op_src1->val + op_src2->val;
	sprintf(assembly, "addu   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
    fprintf(trace_fp, "0x%04x     %02d     0x%04x\n", cpu.pc, op_dest->reg, (op_src1->val + op_src2->val));
}

make_helper(sub)
{
	decode_r_type(instr);
	uint32_t temp = op_src1->val - op_src2->val;
	uint32_t temp1 = (op_src1->val) & 0x80000000;
	uint32_t temp2 = (op_src2->val) & 0x80000000;
	uint32_t temp3 = temp & 0x80000000;
	if ((temp3 == temp2) && (temp3 != temp1))
	{
		// overflow
		// Cause.ExcCode <- 0x0c(Ov)
		cp0_w(R_CAUSE) = (cp0_w(R_CAUSE) & 0xffffff83) | (0x0c << 2);
		// EPC <- pc
		cp0_w(R_EPC) = cpu.pc;
		// Status.EXL <- 1
		cp0_w(R_STATUS) = ((cp0_w(R_STATUS) & 0xfffffffd) | (0x1 << 1));

		// If Ov is in Delay_Slot...
		if (temu_state == JUMP)
		{
			// EPC <- EPC - 4
			cp0_w(R_EPC) -= 4;
			// Cause.BD <- 1
			cp0_w(R_CAUSE) = ((cp0_w(R_CAUSE) & 0x7fffffff) | 0x80000000);
		}

		reg_w(op_dest->reg) = (uint32_t)temp3;
		sprintf(assembly, "sub     %s, %s, %s\nWARNING: Inetger_Overflow Exception.", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
		cpu.pc = 0xbfc00380;
	}
	else
	{
		reg_w(op_dest->reg) = temp;
		sprintf(assembly, "sub   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	    fprintf(trace_fp, "0x%04x     %02d     0x%04x\n", cpu.pc, op_dest->reg, (temp));
	}
}

make_helper(subu)
{
	decode_r_type(instr);
	reg_w(op_dest->reg) = op_src1->val - op_src2->val;
	sprintf(assembly, "subu   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
     fprintf(trace_fp, "0x%04x     %02d     0x%04x\n", cpu.pc, op_dest->reg, (op_src1->val - op_src2->val));
}

make_helper(slt)
{
	decode_r_type(instr);
	int res = ((int)op_src1->val < (int)op_src2->val) ? 1 : 0;
	reg_w(op_dest->reg) = res;
	sprintf(assembly, "slt   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
    fprintf(trace_fp, "0x%04x     %02d     0x%04x\n", cpu.pc, op_dest->reg, (res));
}

make_helper(sltu)
{
	decode_r_type(instr);
	int res  = op_src1->val < op_src2->val ? 1 : 0;
	reg_w(op_dest->reg) = res;
	sprintf(assembly, "sltu  %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
     fprintf(trace_fp, "0x%04x     %02d     0x%04x\n", cpu.pc, op_dest->reg, (res));
}

make_helper(div)
{
	decode_r_type(instr);
	int temp1 = op_src1->val;
	int temp2 = op_src2->val;
	cpu.lo = temp1 / temp2;
	cpu.hi = temp2 % temp2;
	sprintf(assembly, "div  %s,   %s", REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
     fprintf(trace_fp, "0x%04x     %s     0x%04x     %s     0x%04x\n", cpu.pc, "hi", cpu.hi, "lo", cpu.lo);
}

make_helper(divu)
{
	decode_r_type(instr);
	cpu.lo = op_src1->val / op_src2->val;
	cpu.hi = op_src1->val % op_src2->val;
	sprintf(assembly, "divu  %s,   %s", REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
    fprintf(trace_fp, "0x%04x     %s     0x%04x     %s     0x%04x\n", cpu.pc, "hi", cpu.hi, "lo", cpu.lo);
}

make_helper(mult)
{
	decode_r_type(instr);
	uint64_t temp1 = (int)op_src1->val;
	uint64_t temp2 = (int)op_src2->val;
	uint64_t res = temp1 * temp2;
	cpu.lo = res & 0xffffffff;
	cpu.hi = res >> 32;
	sprintf(assembly, "mult  %s,   %s", REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
    fprintf(trace_fp, "0x%04x     %s     0x%04x     %s     0x%04x\n", cpu.pc, "hi", cpu.hi, "lo", cpu.lo);
}

make_helper(multu)
{
	decode_r_type(instr);
	uint64_t temp1 = op_src1->val;
	uint64_t temp2 = op_src2->val;
	uint64_t res = temp1 * temp2;
	cpu.lo = res & 0xffffffff;
	cpu.hi = res >> 32;
	sprintf(assembly, "multu  %s,   %s", REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
    fprintf(trace_fp, "0x%04x     %s     0x%04x     %s     0x%04x\n", cpu.pc, "hi", cpu.hi, "lo", cpu.lo);
}

make_helper(nor)
{
	decode_r_type(instr);
	reg_w(op_dest->reg) = ~(op_src1->val | op_src2->val);
	sprintf(assembly, "nor   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
    fprintf(trace_fp, "0x%04x     %02d     0x%04x\n", cpu.pc, op_dest->reg, (~((op_src1->val | op_src2->val))));
}

make_helper(or)
{
	decode_r_type(instr);
	reg_w(op_dest->reg) = op_src1->val | op_src2->val;
	sprintf(assembly, "or   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
    fprintf(trace_fp, "0x%04x     %02d     0x%04x\n", cpu.pc, op_dest->reg, ((op_src1->val | op_src2->val)));
}

make_helper(xor)
{
	decode_r_type(instr);
	reg_w(op_dest->reg) = op_src1->val ^ op_src2->val;
	sprintf(assembly, "xor   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
    fprintf(trace_fp, "0x%04x     %02d     0x%04x\n", cpu.pc, op_dest->reg, (op_src1->val ^ op_src2->val));
}

make_helper(sllv)
{
	decode_r_type(instr);
	reg_w(op_dest->reg) = (op_src2->val << ((op_src1->val << (32 - RS_SIZE)) >> (32 - RS_SIZE)));
	sprintf(assembly, "sllv   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg), REG_NAME(op_src1->reg));
    fprintf(trace_fp, "0x%04x     %02d     0x%04x\n", cpu.pc, op_dest->reg, (op_src2->val << ((op_src1->val << (32 - RS_SIZE)) >> (32 - RS_SIZE))));
}

make_helper(sll)
{
	decode_r_type(instr);
	uint32_t sa = (instr & SHAMT_MASK) >> FUNC_SIZE;
	reg_w(op_dest->reg) = (op_src2->val << sa);
	sprintf(assembly, "sll   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg), sa);
     fprintf(trace_fp, "0x%04x     %02d     0x%04x\n", cpu.pc, op_dest->reg, (op_src2->val) << sa);
} 

make_helper(srav)
{
	decode_r_type(instr);
	reg_w(op_dest->reg) = (((int)op_src2->val) >> ((op_src1->val << (32 - RS_SIZE)) >> (32 - RS_SIZE)));
	sprintf(assembly, "srav   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg), REG_NAME(op_src1->reg));
    fprintf(trace_fp, "0x%04x     %02d     0x%04x\n", cpu.pc, op_dest->reg, (((int)op_src2->val) >> ((op_src1->val << (32 - RS_SIZE)) >> (32 - RS_SIZE))));
}

make_helper(sra)
{
	decode_r_type(instr);
	uint32_t sa = (instr & SHAMT_MASK) >> FUNC_SIZE;
	reg_w(op_dest->reg) = (((int)op_src2->val) >> sa);
	sprintf(assembly, "sra   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg), sa);
    fprintf(trace_fp, "0x%04x     %02d     0x%04x\n", cpu.pc, op_dest->reg, (int)(op_src2->val) >> sa);
}

make_helper(srlv)
{
	decode_r_type(instr);
	reg_w(op_dest->reg) = (op_src2->val >> ((op_src1->val << (32 - RS_SIZE)) >> (32 - RS_SIZE)));
	sprintf(assembly, "srlv   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg), REG_NAME(op_src1->reg));
    fprintf(trace_fp, "0x%04x     %02d     0x%04x\n", cpu.pc, op_dest->reg,  (op_src2->val >> ((op_src1->val << (32 - RS_SIZE)) >> (32 - RS_SIZE))));
}

make_helper(srl)
{
	decode_r_type(instr);
	uint32_t sa = (instr & SHAMT_MASK) >> FUNC_SIZE;
	reg_w(op_dest->reg) = (op_src2->val >> sa);
	sprintf(assembly, "srl   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg), sa);
    fprintf(trace_fp, "0x%04x     %02d     0x%04x\n", cpu.pc, op_dest->reg, (op_src2->val) >> sa);
}

make_helper(jr)
{
	decode_r_type(instr);
	int target_offset = op_src1->val - 4;
	cpu.pc = target_offset;
	sprintf(assembly, "jr   %s", REG_NAME(op_src1->reg));
	temu_state = JUMP;
}

make_helper(jalr)
{
	decode_r_type(instr);
	reg_w(op_dest->reg) = cpu.pc + 8;
	int target_offset = op_src1->val - 4;
	cpu.pc = target_offset;
	sprintf(assembly, "jalr   %s   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg));
	temu_state = JUMP;
}

make_helper(mfhi)
{
	decode_r_type(instr);
	reg_w(op_dest->reg) = cpu.hi;
	sprintf(assembly, "mfhi   %s", REG_NAME(op_dest->reg));
    fprintf(trace_fp, "0x%04x     %02d     0x%04x\n", cpu.pc, op_dest->reg, cpu.hi);
}

make_helper(mflo)
{
	decode_r_type(instr);
	reg_w(op_dest->reg) = cpu.lo;
	sprintf(assembly, "mflo   %s", REG_NAME(op_dest->reg));
    fprintf(trace_fp, "0x%04x     %02d     0x%04x\n", cpu.pc, op_dest->reg, cpu.lo);
}

make_helper(mthi)
{
	decode_r_type(instr);
	cpu.hi = op_src1->val;
	sprintf(assembly, "mthi   %s", REG_NAME(op_src1->reg));
    fprintf(trace_fp, "0x%04x     %s     0x%04x\n", cpu.pc, "hi", op_src1->val);
}

make_helper(mtlo)
{
	decode_r_type(instr);
	cpu.lo = op_src1->val;
	sprintf(assembly, "mtlo   %s", REG_NAME(op_src1->reg));
    fprintf(trace_fp, "0x%04x     %s     0x%04x\n", cpu.pc, "lo", op_src1->val);
}

make_helper(mips_break)
{
	decode_r_type(instr);
	uint32_t code = (instr & 0x03ffffc0) >> FUNC_SIZE;

	// Breakpoint Exception
	// Cause.ExcCode <- 0x09(Bp)
	cp0_w(R_CAUSE) = (cp0_w(R_CAUSE) & 0xffffff83) | (0x09 << 2);
	// EPC <- pc
	cp0_w(R_EPC) = cpu.pc;
	// Status.EXL <- 1
	cp0_w(R_STATUS) = ((cp0_w(R_STATUS) & 0xfffffffd) | (0x1 << 1));

	// If Bp is in Delay_Slot...
	if (temu_state == JUMP)
	{
		// EPC <- EPC - 4
		cp0_w(R_EPC) -= 4;
		// Cause.BD <- 1
		cp0_w(R_CAUSE) = ((cp0_w(R_CAUSE) & 0x7fffffff) | 0x80000000);
	}

	sprintf(assembly, "break   0x%x", code);
	cpu.pc = 0xbfc00380;
	temu_state = STOP;
}

make_helper(mips_syscall)
{
	cp0.epc = cpu.pc;
	cp0.status = 0x2;
	cp0.cause = (SYS << 2);
	cpu.pc = EXCEPTION - 4;
	sprintf(assembly, "syscall");
}